using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Globalization;
using System.Text.RegularExpressions;

namespace Xnlab.Filio
{
    #region Networking
    internal class FTPDirectoryListItem
    {
        private string flags;
        private string owner;
        private string group;
        private bool isDirectory;
        private bool isSymLink;
        private string name;
        private DateTime creationTime;
        private string symLinkTargetPath;
        private long length;

        public string SymLinkTargetPath
        {
            get { return symLinkTargetPath; }
            set { symLinkTargetPath = value; }
        }

        public string Flags
        {
            get { return flags; }
            set { flags = value; }
        }

        public string Owner
        {
            get { return owner; }
            set { owner = value; }
        }

        public long Length
        {
            get { return length; }
            set { length = value; }
        }

        public string Group
        {
            get { return group; }
            set { group = value; }
        }

        public bool IsDirectory
        {
            get { return isDirectory; }
            set { isDirectory = value; }
        }

        public bool IsSymLink
        {
            get { return isSymLink; }
            set { isSymLink = value; }
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public DateTime CreationTime
        {
            get { return creationTime; }
            set { creationTime = value; }
        }
    }

    /// <summary>
    /// Based on Adarsh's code: http://blogs.msdn.com/adarshk/archive/2004/09/15/230177.aspx
    /// </summary>
    internal class FTPDirectoryListParser
    {
        enum EDirectoryListingStyle { UnixStyle, WindowsStyle, Unknown }

        const string unixSymLinkPathSeparator = " -> ";

        public static List<FTPDirectoryListItem> GetDirectoryList(string datastring)
        {
            try
            {
                List<FTPDirectoryListItem> myListArray = new List<FTPDirectoryListItem>();
                string[] dataRecords = datastring.Split(new string[] { datastring.IndexOf("\r\n") == -1 ? "\n" : "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                EDirectoryListingStyle _directoryListStyle = GuessDirectoryListingStyle(dataRecords);
                foreach (string s in dataRecords)
                {
                    if (_directoryListStyle != EDirectoryListingStyle.Unknown && s != "")
                    {
                        FTPDirectoryListItem f = new FTPDirectoryListItem();
                        f.Name = "..";
                        switch (_directoryListStyle)
                        {
                            case EDirectoryListingStyle.UnixStyle:
                                f = ParseDirectoryListItemFromUnixStyleRecord(s);
                                break;
                            case EDirectoryListingStyle.WindowsStyle:
                                f = ParseDirectoryListItemFromWindowsStyleRecord(s);
                                break;
                        }
                        if (!(f == null || f.Name == "." || f.Name == ".."))
                        {
                            myListArray.Add(f);
                        }
                    }
                }
                return myListArray; ;
            }
            catch (Exception)
            {
                return null;
            }
        }

        private static FTPDirectoryListItem ParseDirectoryListItemFromWindowsStyleRecord(string Record)
        {
            ///Assuming the record style as
            /// 02-03-04  07:46PM       <DIR>          Append
            FTPDirectoryListItem f = new FTPDirectoryListItem();
            string processstr = Record.Trim();
            string dateStr = processstr.Substring(0, 8);
            processstr = (processstr.Substring(8, processstr.Length - 8)).Trim();
            string timeStr = processstr.Substring(0, 7);
            processstr = (processstr.Substring(7, processstr.Length - 7)).Trim();
            f.CreationTime = DateTime.Parse(dateStr + " " + timeStr, CultureInfo.GetCultureInfo("en-US"));
            if (processstr.Substring(0, 5) == "<DIR>")
            {
                f.IsDirectory = true;
                processstr = (processstr.Substring(5, processstr.Length - 5)).Trim();
            }
            else
            {
                //string[] strs = processstr.Split(new char[] { ' ' }, true);
                //processstr = strs[1].Trim();
                int index = processstr.IndexOf(' ');
                f.Length = Convert.ToInt64(processstr.Substring(0, index));
                processstr = processstr.Remove(0, index + 1);
                f.IsDirectory = false;
            }
            f.Name = processstr;  //Rest is name   
            return f;
        }

        private static EDirectoryListingStyle GuessDirectoryListingStyle(string[] recordList)
        {
            foreach (string s in recordList)
            {
                if (s.Length > 10
                 && Regex.IsMatch(s.Substring(0, 10), "(-|d)(-|r)(-|w)(-|x)(-|r)(-|w)(-|x)(-|r)(-|w)(-|x)"))
                {
                    return EDirectoryListingStyle.UnixStyle;
                }
                else if (s.Length > 8
                 && Regex.IsMatch(s.Substring(0, 8), "[0-9][0-9]-[0-9][0-9]-[0-9][0-9]"))
                {
                    return EDirectoryListingStyle.WindowsStyle;
                }
            }
            return EDirectoryListingStyle.Unknown;
        }

        private static FTPDirectoryListItem ParseDirectoryListItemFromUnixStyleRecord(string Record)
        {
            ///Assuming record style as
            /// dr-xr-xr-x   1 owner    group               0 Nov 25  2002 bussys

            // Mac OS X - tnftpd returns the total on the first line
            if (Record.ToLower().StartsWith("total "))
                return null;

            FTPDirectoryListItem f = new FTPDirectoryListItem();
            string processstr = Record.Trim();
            f.Flags = processstr.Substring(0, 9);
            f.IsDirectory = (f.Flags[0] == 'd');
            // Note: there is no way to determine here if the symlink refers to a dir or a file
            f.IsSymLink = (f.Flags[0] == 'l');
            processstr = (processstr.Substring(11)).Trim();
            CutSubstringFromStringWithTrim(ref processstr, " ", 0);   //skip one part
            f.Owner = CutSubstringFromStringWithTrim(ref processstr, " ", 0);
            f.Group = CutSubstringFromStringWithTrim(ref processstr, " ", 0);
            f.Length = Convert.ToInt64(CutSubstringFromStringWithTrim(ref processstr, " ", 0));   //skip one part

            string creationTimeStr = CutSubstringFromStringWithTrim(ref processstr, " ", 8);
            string dateFormat;
            if (creationTimeStr.IndexOf(':') < 0)
                dateFormat = "MMM dd  yyyy";
            else
                dateFormat = "MMM dd HH:mm";

            // Some servers (e.g.: Mac OS X 10.5 - tnftpd) return days < 10 without a leading 0 
            if (creationTimeStr[4] == ' ')
                creationTimeStr = creationTimeStr.Substring(0, 4) + "0" + creationTimeStr.Substring(5);

            f.CreationTime = DateTime.ParseExact(creationTimeStr, dateFormat, CultureInfo.GetCultureInfo("en-US"));

            if (f.IsSymLink && processstr.IndexOf(unixSymLinkPathSeparator) > 0)
            {
                f.Name = CutSubstringFromStringWithTrim(ref processstr, unixSymLinkPathSeparator, 0);
                f.SymLinkTargetPath = processstr;
            }
            else
                f.Name = processstr;   //Rest of the part is name
            return f;
        }

        private static string CutSubstringFromStringWithTrim(ref string s, string str, int startIndex)
        {
            int pos1 = s.IndexOf(str, startIndex);
            string retString = s.Substring(0, pos1);
            s = (s.Substring(pos1 + str.Length)).Trim();
            return retString;
        }
    }

    // State object for receiving data from remote device.
    internal class StateObject
    {
        // Client socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 4096;//1024
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
        // Received data string.
        public List<byte> content = new List<byte>();
    }

    public enum CancelModes
    {
        None,
        CancelRequest,
        Shutdown
    }

    public class DataTransferEventArgs : DataEventArgs
    {
        private CancelModes cancel = CancelModes.None;
        public CancelModes Cancel
        {
            get { return cancel; }
            set { cancel = value; }
        }

        public DataTransferEventArgs(StateTransfer Worker, byte[] Buffer)
        {
            this.Buffer = Buffer;
            worker = Worker;
        }

        public DataTransferEventArgs(StateTransfer Worker, string Content)
        {
            this.Content = Content;
            worker = Worker;
        }

        private StateTransfer worker;
        public StateTransfer Worker
        {
            get { return worker; }
            set { worker = value; }
        }
    }

    public abstract class StateTransfer
    {
        private object state;
        public object State
        {
            get { return state; }
            set { state = value; }
        }

        public abstract void Receive();

        public abstract void Send(CompressionMethods CompressionMethod, string Data);

        public abstract void Send(CompressionMethods CompressionMethod, byte[] Data);
    }

    internal abstract class AsynchronousSocket : StateTransfer
    {
        public event EventHandler<DataTransferEventArgs> Connected;
        public event EventHandler<DataTransferEventArgs> Failed;
        public event EventHandler<DataTransferEventArgs> Sent;
        public event EventHandler<DataTransferEventArgs> Received;

        protected internal void OnConnected(string Content)
        {
            if (Connected != null)
                Connected(this, new DataTransferEventArgs(this, Content));
        }

        protected internal void OnFailed(string Content)
        {
            if (Failed != null)
                Failed(this, new DataTransferEventArgs(this, Content));
        }

        protected internal void OnSent(string Content)
        {
            if (Sent != null)
                Sent(this, new DataTransferEventArgs(this, Content));
        }

        protected internal void OnReceived(byte[] Content, out byte[] Response)
        {
            Response = null;
            if (Received != null)
            {
                DataTransferEventArgs e = new DataTransferEventArgs(this, Content);
                Received(this, e);
                Response = e.Buffer;
            }
        }

        protected bool AcceptBuffer(StateObject State, int BytesRead, out byte[] Buffer)
        {
            // There might be more data, so store the data received so far.
            byte[] buffer = new byte[BytesRead];
            Array.Copy(State.buffer, buffer, BytesRead);
            State.content.AddRange(buffer);
            int matchCount = 0;
            if (State.content.Count > WorkUtils.DataEndings.Length)
            {
                for (int i = 0; i < WorkUtils.DataEndings.Length; i++)
                {
                    if (State.content[State.content.Count - 1 - i] == WorkUtils.DataEndings[WorkUtils.DataEndings.Length - 1 - i])
                        matchCount++;
                }
            }
            if (matchCount == WorkUtils.DataEndings.Length)
            {
                buffer = new byte[State.content.Count - WorkUtils.DataEndings.Length];
                State.content.CopyTo(0, buffer, 0, buffer.Length);
                Buffer = WorkUtils.DecompressBuffer(buffer);
                return true;
            }
            else
            {
                Buffer = null;
                return false;
            }
        }
    }

    internal class AsynchronousClient : AsynchronousSocket
    {
        private Socket client = null;

        public void Connect(string URI, int Port)
        {
            // Connect to a remote device.
            try
            {
                // Establish the remote endpoint for the socket.
                // The name of the 
                //IPHostEntry ipHostInfo = Dns.GetHostEntry(URI);
                //IPAddress ipAddress = ipHostInfo.AddressList[0];
                IPAddress ipAddress = null;
                IPAddress[] addresses = Dns.GetHostAddresses(URI);
                foreach (IPAddress address in addresses)
                {
                    if (address.AddressFamily == AddressFamily.InterNetwork)
                    {
                        ipAddress = address;
                        break;
                    }
                }
                if (ipAddress == null)
                    ipAddress = addresses[0];
                IPEndPoint remoteEP = new IPEndPoint(ipAddress, Port);

                // Create a TCP/IP socket.
                client = new Socket(AddressFamily.InterNetwork,
                    SocketType.Stream, ProtocolType.Tcp);

                // Connect to the remote endpoint.
                client.BeginConnect(remoteEP,
                    new AsyncCallback(ConnectCallback), client);
            }
            catch (Exception ex)
            {
                OnFailed(ex.ToString());
            }
        }

        public override void Send(CompressionMethods CompressionMethod, string Data)
        {
            Send(CompressionMethod, Encoding.UTF8.GetBytes(Data));
        }

        public override void Send(CompressionMethods CompressionMethod, byte[] Data)
        {
            Send(client, CompressionMethod, Data);
        }

        public void Close()
        {
            // Release the socket.
            if (client != null)
            {
                client.Shutdown(SocketShutdown.Both);
                client.Close();
            }
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket client = (Socket)ar.AsyncState;

                // Complete the connection.
                client.EndConnect(ar);
                OnConnected(client.RemoteEndPoint.ToString());
            }
            catch (Exception ex)
            {
                OnFailed(ex.ToString());
            }
        }

        public override void Receive()
        {
            try
            {
                // Create the state object.
                StateObject state = new StateObject();
                state.workSocket = client;

                // Begin receiving the data from the remote device.
                client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReceiveCallback), state);
            }
            catch (Exception ex)
            {
                OnFailed(ex.ToString());
            }
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the state object and the client socket 
                // from the asynchronous state object.
                StateObject state = (StateObject)ar.AsyncState;
                Socket client = state.workSocket;

                // Read data from the remote device.
                int bytesRead = client.EndReceive(ar);

                if (bytesRead > 0)
                {
                    byte[] buffer;
                    if (AcceptBuffer(state, bytesRead, out buffer))
                    {
                        // All the data has arrived
                        byte[] response;
                        OnReceived(buffer, out response);

                        state = new StateObject();
                        state.workSocket = client;
                        client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                            new AsyncCallback(ReceiveCallback), state);
                    }
                    else
                    {
                        // Get the rest of the data.
                        client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                            new AsyncCallback(ReceiveCallback), state);
                    }
                }
            }
            catch (Exception ex)
            {
                OnFailed(ex.ToString());
            }
        }

        private void Send(Socket client, CompressionMethods CompressionMethod, byte[] Data)
        {
            List<byte> buffer = new List<byte>();
            buffer.AddRange(BitConverter.GetBytes((int)CompressionMethod));
            buffer.AddRange(BitConverter.GetBytes(Data.Length));
            if (CompressionMethod != CompressionMethods.None)
                WorkUtils.Compress(CompressionMethod, ref Data);
            buffer.AddRange(Data);
            buffer.AddRange(WorkUtils.DataEndings);
            client.BeginSend(buffer.ToArray(), 0, buffer.Count, 0,
                new AsyncCallback(SendCallback), client);
        }

        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket client = (Socket)ar.AsyncState;

                // Complete sending the data to the remote device.
                int bytesSent = client.EndSend(ar);

                OnSent(null);
            }
            catch (Exception ex)
            {
                OnFailed(ex.ToString());
            }
        }
    }


    internal class AsynchronousSocketListener : AsynchronousSocket
    {
        // Thread signal.
        private ManualResetEvent allDone = new ManualResetEvent(false);
        private Socket listener = null;
        private bool finished = false;

        public AsynchronousSocketListener()
        {
        }

        public void Start(int Port)
        {
            // Establish the local endpoint for the socket.
            // The DNS name of the computer
            IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
            IPAddress ipAddress = IPAddress.Any;
            //foreach (IPAddress address in ipHostInfo.AddressList)
            //{
            //    if (address.AddressFamily == AddressFamily.InterNetwork)
            //    {
            //        ipAddress = address;
            //        break;
            //    }
            //}
            //if (ipAddress == null)
            //    ipAddress = ipHostInfo.AddressList[0];
            IPEndPoint localEndPoint = new IPEndPoint(ipAddress, Port);

            // Create a TCP/IP socket.
            listener = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);

            // Bind the socket to the local endpoint and listen for incoming connections.
            try
            {
                listener.Bind(localEndPoint);
                listener.Listen(100);

                while (!finished)
                {
                    // Set the event to nonsignaled state.
                    allDone.Reset();

                    listener.BeginAccept(
                        new AsyncCallback(AcceptCallback),
                        listener);

                    // Wait until a connection is made before continuing.
                    allDone.WaitOne();
                }
            }
            catch (Exception ex)
            {
                OnFailed(ex.ToString());
            }
        }

        public void Close()
        {
            finished = true;
            if (listener != null)
                listener.Close();
            allDone.Set();
        }

        public void AcceptCallback(IAsyncResult ar)
        {
            // Signal the main thread to continue.
            allDone.Set();

            try
            {
                // Get the socket that handles the client request.
                Socket listener = (Socket)ar.AsyncState;
                Socket handler = listener.EndAccept(ar);

                OnConnected(handler.RemoteEndPoint.ToString());

                // Create the state object.
                StateObject state = new StateObject();
                state.workSocket = handler;
                handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReadCallback), state);

            }
            catch (Exception ex)
            {
                OnFailed(ex.ToString());//conection lost
            }
        }

        public override void Receive()
        {

        }

        public override void Send(CompressionMethods CompressionMethod, string Data)
        {

        }

        public override void Send(CompressionMethods CompressionMethod, byte[] Data)
        {

        }

        public void ReadCallback(IAsyncResult ar)
        {
            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject state = (StateObject)ar.AsyncState;
            Socket handler = state.workSocket;

            try
            {
                // Read data from the client socket. 
                int bytesRead = handler.EndReceive(ar);

                if (bytesRead > 0)
                {
                    byte[] buffer;
                    if (AcceptBuffer(state, bytesRead, out buffer))
                    {
                        byte[] response;
                        OnReceived(buffer, out response);
                        if (response != null)
                            Send(handler, response);

                        state = new StateObject();
                        state.workSocket = handler;
                        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                            new AsyncCallback(ReadCallback), state);
                    }
                    else
                    {
                        // Not all data received. Get more.
                        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                        new AsyncCallback(ReadCallback), state);
                    }
                }

            }
            catch (Exception ex)
            {
                OnFailed(ex.ToString());//conection lost
                //finished = true;
            }
        }

        private void Send(Socket handler, string data)
        {
            Send(handler, Encoding.UTF8.GetBytes(data));
        }

        private void Send(Socket handler, byte[] data)
        {
            CompressionMethods compressionMethod = WorkSettings.Instance.CompressionMethod;
            List<byte> buffer = new List<byte>();
            buffer.AddRange(BitConverter.GetBytes((int)compressionMethod));
            buffer.AddRange(BitConverter.GetBytes(data.Length));
            if (compressionMethod != CompressionMethods.None)
                WorkUtils.Compress(compressionMethod, ref data);
            buffer.AddRange(data);
            buffer.AddRange(WorkUtils.DataEndings);

            // Begin sending the data to the remote device.
            handler.BeginSend(buffer.ToArray(), 0, buffer.Count, 0,
                new AsyncCallback(SendCallback), handler);
        }

        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket handler = (Socket)ar.AsyncState;

                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);

                OnSent(null);

            }
            catch (Exception ex)
            {
                OnFailed(ex.ToString());
            }
        }
    }
    #endregion
}
